﻿/*
*Copyright (c) 2011 Seth Ballantyne <seth.ballantyne@gmail.com>
*
*Permission is hereby granted, free of charge, to any person obtaining a copy
*of this software and associated documentation files (the "Software"), to deal
*in the Software without restriction, including without limitation the rights
*to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*copies of the Software, and to permit persons to whom the Software is
*furnished to do so, subject to the following conditions:
*
*The above copyright notice and this permission notice shall be included in
*all copies or substantial portions of the Software.
*
*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using EraserBotFrontend.Paths;

/*
 * TODO: Create a better way of referencing the individual tab pages, rather
 * than simply using their index.
 * 
 */
namespace EraserBotFrontend
{
    /// <summary>
    /// Class containing the application itself. This file contains the events
    /// generated by the GUI designer, see MainFormCustomMethods.cs for ... methods.
    /// </summary>
    public partial class MainForm : Form
    {
        // holds the deathmatch specific controls
        DMSettings dmSettings = null;

        TeamDMSettings teamDMSettings = null;

        // forms containing the controls for the "specific bots" and "random bots"
        // options. Controls are copied from the forms based on which option the
        // user selects. When the user selects an option, the controls are copied
        // to the relevent control array below, and then rendered on the form.
        SpecificBotSelection specificBotSelection = new SpecificBotSelection();
        RandomBotSelection randomBotSelection = new RandomBotSelection();

        // stores the controls located on the specificBotSelection form
        Control[] specificBotControls;

        // stores the controls located on the randomBotSelection form
        Control[] randomBotControls;

        // stores the bots read from bots.cfg
        //Bot[] bots = null;

        BotFileOutput botFileContents;

        // models stored in the baseq2/players directory. Models should only
        // be stored in this directory if they're located in a directory
        // that contains valid skins, and a model file called tris.md2.
        // The name of the model is based on the name of the directory in which 
        // the skins and *.md2 are located 
        List<Model> availableModels = new List<Model>();

        MD2Viewer modelViewer;
        int lastBotSelectionComboBoxIndex = -1;

        string modelName;
        string skinName;

        bool disableMD2Viewer = false;
        //string q2Path = String.Empty;
        //string eraserPath = String.Empty;

        // used to keep track of which controls are being rendered in the
        // match settings tab. This information is needed for when it's time
        // to clear the current controls and add the controls of the newly
        // selected match type.
        int currentSelectedIndex = -1;

        Control[] controlBuffer = null;

        // keeps track of the index of the specified tab. See the GetTabIndex method.
        int playerTabIndex = -1;
        int botsTabIndex = -1;
        int matchSettingsTabIndex = -1;
        int teamsTabIndex = -1;

        bool teamTabIsHidden = true;
        bool botTabIsHidden = false;

        TabBuffer tabBufferForm = new TabBuffer();

        MatchType selectedMatchType;

        string version;
        
        public MainForm()
        {
            InitializeComponent();

            // whatever happened to using a string constant like "#define APP_VERSION 1.3"
            string majorVersion = Convert.ToString(Assembly.GetExecutingAssembly().GetName().Version.Major);
            string minorVersion = Convert.ToString(Assembly.GetExecutingAssembly().GetName().Version.Minor);

            version = majorVersion + "." + minorVersion;
            this.Text += " " + version;
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            if (Quake2Paths.Root == String.Empty || 
                EraserPaths.Base == String.Empty)
            {
                MessageBox.Show(
                    Resource.FirstRunMessage, 
                    Resource.FirstRunTitle, 
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information
                    );

                this.WindowState = FormWindowState.Minimized;

                PathConfiguration pathConfiguration = new PathConfiguration(this);
                pathConfiguration.ShowDialog();
            }
            else
            {
                Init();
            }

            
        }

        private void launchButton_Click(object sender, EventArgs e)
        {
            // -----------------------------------------------------------------------------------------
            // This method really needs some refactoring. Seriously.
            // Break up the command line generation and the execution code. 
            // It might even be a good idea to seperate each aspect of the generation code (DM, TeamDM).
            // -----------------------------------------------------------------------------------------

            if (EraserPaths.Base == string.Empty ||
               Quake2Paths.Root == string.Empty)
            {
                MessageBox.Show(
                    Resource.LaunchPathsNotSet,
                    Resource.UnableToContinue,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error
                    );

                return;
            }
            if (selectedMapsListBox.Items.Count == 0)
            {
                MessageBox.Show(Resource.NoMapsSelected, Resource.NoMapsSelectedTitle, MessageBoxButtons.OK,
                    MessageBoxIcon.Information);

                return;
            }
 
            StringBuilder quake2CmdLineArgs = new StringBuilder();
            
            quake2CmdLineArgs.Append(@"+set game Eraser ");

            // map has to be set before you can set teamplay. If you don't, Quake II
            // will plonk you in deathmatch mode. The reason is you can't execute
            // "cmd" commands when your not in a game (cmd join <team name> in this instance)
            quake2CmdLineArgs.Append(GenerateMapArgs());

            int index = matchTypeComboBox.SelectedIndex;
            if (index == -1)
            {
                MessageBox.Show(
                    "A match type hasn't been selected. Click on the 'Match Settings' tab and select a match." +
                    "",
                    "Unable to continue", 
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information
                    );

                return;
            }
            string matchType = matchTypeComboBox.Items[index].ToString().ToLower();

            #region Deathmatch specific
            if (selectedMatchType == MatchType.Deathmatch)
            {
                if (!BotsAreSelected())
                {
                    DialogResult dr = MessageBox.Show(Resource.NoBotsSelected,
                        Resource.ConfirmationNeeded, MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                    if (dr == DialogResult.No)
                        return;
                }

                quake2CmdLineArgs.Append(dmSettings.GenerateDeathmatchArgs());
            }
            #endregion
            else if (selectedMatchType == MatchType.TeamDeathmatch) 
            {
                quake2CmdLineArgs.Append("+set teamplay 1 ");

                quake2CmdLineArgs.AppendFormat("+set players_per_team {0} ",
                    memberCountNumericUpDown.Value);

                if (playerTeamComboBox.SelectedIndex < 0)
                {
                    MessageBox.Show(Resource.PlayerHasntJoinedATeam, "",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);

                    return;
                }
                
                int i = playerTeamComboBox.SelectedIndex;
                string playerTeam = playerTeamComboBox.Items[i] as string;
                Team[] checkedTeams = teamListView.CheckedTeams();

                if (checkedTeams.Length == 0)
                {
                    MessageBox.Show(Resource.NoOpposingTeams, "",
                        MessageBoxButtons.OK, MessageBoxIcon.Error);

                    return;
                }

                // temp buffer. The contents will be compared against
                // the players chosen team and if there's match found,
                // removed from the buffer. If there's any remaining teams
                // left in the buffer they will be added as arguments
                // to the "sv teams" command. If the buffer is empty,
                // the user is informed to choose another team.
                List<string> opposingTeams = new List<string>();
                foreach (Team team in checkedTeams)
                {
                    if (team.Name != playerTeam)
                    {
                        opposingTeams.Add(team.Name);
                    }
                }

                if (opposingTeams.Count > 0)
                {
                    StringBuilder teams = new StringBuilder();

                    foreach (string s in opposingTeams)
                    {
                        teams.Append(s + " ");
                    }
                    quake2CmdLineArgs.AppendFormat("+cmd join {0} ", playerTeam);
                    quake2CmdLineArgs.AppendFormat("+sv teams {0}", teams.ToString());
                }
                else
                {
                    MessageBox.Show(Resource.PlayerOppositionTeamsSame,
                        "", MessageBoxButtons.OK, MessageBoxIcon.Error);

                    return;
                }

                quake2CmdLineArgs.Append(teamDMSettings.GenerateTeamDMArgs());
            }

            

            // only need bot args for deathmatch
            if(selectedMatchType == MatchType.Deathmatch)
                quake2CmdLineArgs.Append(GenerateBotArgs());

            quake2CmdLineArgs.Append(GeneratePlayerArgs());
            
            try
            {
                Process q2Process = new Process();

                q2Process.StartInfo.FileName = Quake2Paths.Quake2Exe;
                q2Process.StartInfo.Arguments = quake2CmdLineArgs.ToString();

                // working directory has to be Quake II's root directory,
                // else Quake II throws an error message and exits
                q2Process.StartInfo.WorkingDirectory = Quake2Paths.Root;

                q2Process.Start();

            }
            catch (Win32Exception win32E)
            {
                MessageBox.Show(win32E.Message);
            }
            
        }

        private void matchTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Selected a match type that differs from the current one, so clear
            // the tab page of all controls, except for the first (the match type listbox).
            // Has to be done because some match types have settings only applicable to them.
            if (matchSettingsTabIndex == -1)
            {
                matchTypeComboBox.SelectedIndex = -1;

                return;
            }

            if (currentSelectedIndex != -1)
            {
                for (int i = 0; i < controlBuffer.Length; i++)
                {
                    tabControl.TabPages[matchSettingsTabIndex].Controls.Remove(controlBuffer[i]);
                }
            }

            // Deathmatch
            if (matchTypeComboBox.SelectedIndex == 0)
            {
                selectedMatchType = MatchType.Deathmatch;

                dmSettings = new DMSettings();
                controlBuffer = new Control[dmSettings.Controls.Count];
                
                dmSettings.Controls.CopyTo(controlBuffer, 0);
                this.tabControl.TabPages[matchSettingsTabIndex].Controls.AddRange(controlBuffer);

                if (botTabIsHidden == true)
                {
                    botTabIsHidden = false;
                    // Add the bots tab
                    tabControl.TabPages.Insert(botsTabIndex,
                        tabBufferForm.GetTab(Resource.BotsTabText));

                    tabBufferForm.AddTab(tabControl.TabPages[teamsTabIndex]);
                    teamTabIsHidden = true;
                }

                botSelectionComboBox.SelectedIndex = 0;
            }
            // Team Deathmatch
            else if (matchTypeComboBox.SelectedIndex == 1)
            {
                selectedMatchType = MatchType.TeamDeathmatch;

                teamDMSettings = new TeamDMSettings();
                controlBuffer = new Control[teamDMSettings.Controls.Count];

                teamDMSettings.Controls.CopyTo(controlBuffer, 0);

                this.tabControl.TabPages[matchSettingsTabIndex].Controls.AddRange(controlBuffer);

                if (teamTabIsHidden == true)
                {
                    //BotTeamSelection bts = new BotTeamSelection();

                    tabControl.TabPages.Insert(teamsTabIndex, 
                        tabBufferForm.GetTab(Resource.TeamTabText));

                    //controlBuffer = new Control[bts.Controls.Count];
                    //bts.Controls.CopyTo(controlBuffer, 0);
                    //tabControl.TabPages[teamsTabIndex].Controls.AddRange(controlBuffer);

                    teamTabIsHidden = false;

                    tabBufferForm.AddTab(tabControl.TabPages[botsTabIndex]);
                    botTabIsHidden = true;
                }
            }

            currentSelectedIndex = matchTypeComboBox.SelectedIndex;
        }

        private void configurationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PathConfiguration pathConfiguration = new PathConfiguration(this);
            pathConfiguration.ShowDialog();
        }

        private void botSelectionComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lastBotSelectionComboBoxIndex == botSelectionComboBox.SelectedIndex)
                return;
            // random bots
            if (botSelectionComboBox.SelectedIndex == 0)
            {
                HideAlternateOptionControls(specificBotControls, this.tabControl.TabPages[2]);
                AllocateBufferIfUnallocated(ref randomBotControls, randomBotSelection);
                
                this.tabControl.TabPages[2].Controls.AddRange(randomBotControls);

                // keep the selected skill despite the method of bot generation.
                // this has to be updated everyime the generation method changes 
                // because of the fact the controls are inherited, and won't automatically
                // update like controls copied from forms
                randomBotSelection.Skill = specificBotSelection.Skill;
              //  lastBotSelectionComboBoxIndex = botSelectionComboBox.SelectedIndex;
            }
            // specific bots
            else if (botSelectionComboBox.SelectedIndex == 1)
            {
                HideAlternateOptionControls(randomBotControls, this.tabControl.TabPages[2]);
                AllocateBufferIfUnallocated(ref specificBotControls, specificBotSelection);
                
                this.tabControl.TabPages[2].Controls.AddRange(specificBotControls);

                // as above
                
                specificBotSelection.Skill = randomBotSelection.Skill;
            }

            lastBotSelectionComboBoxIndex = botSelectionComboBox.SelectedIndex;
        }

        private void modelComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (modelComboBox.SelectedIndex >= 0)
            {
                int index = modelComboBox.SelectedIndex;

                modelName = modelComboBox.Items[index].ToString();

                if (!disableMD2Viewer)
                {
                    modelViewer.Model = Quake2Paths.Player + modelName + "\\tris.md2";
                }

                skinComboBox.Items.Clear();
                skinComboBox.Items.AddRange(availableModels[index].Skins);

                // given the checks made when availableModels is being built this should
                // always be true, but just incase
                if (skinComboBox.Items.Count > 0)
                {
                    skinComboBox.SelectedIndex = 0;
                }
            }
        }

        private void skinComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if(skinComboBox.SelectedIndex >= 0)
            {
                skinName = skinComboBox.SelectedItem as string;
                if (!disableMD2Viewer)
                {
                    modelViewer.Skin = Quake2Paths.Player + modelName + @"\\" + skinName + ".pcx";
                }
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if(!disableMD2Viewer)
                modelViewer.Update();
        }

        private void nameTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            // spaces are not allowed in the user name. Including a space
            // will cause Quake II to incorrectly parse any following 
            // command line arguments
            if (e.KeyChar == ' ')
            {
                e.KeyChar = '_';
            }
        }
        

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            About aboutForm = new About(version);
            aboutForm.ShowDialog();
        }

        private void availableMapsTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            // if a parent node has one of more child nodes, allow the
            // user to add all the child nodes to selectedMapsListBox
            if (availableMapsTreeView.SelectedNode.Nodes.Count > 0)
            {
                availableMapsAddAllButton.Enabled = true;
            }
            else
            {
                availableMapsAddAllButton.Enabled = false;
            }

            if (availableMapsTreeView.SelectedNode.Level > 0)
            {
                availableMapsAddButton.Enabled = true;
                availableMapsAddAllButton.Enabled = true;
            }
            else
            {
                availableMapsAddButton.Enabled = false;
                
            }
        }

        private void availableMapsAddButton_Click(object sender, EventArgs e)
        {
            if (availableMapsTreeView.SelectedNode.Level > 0)
            {
                string mapName = availableMapsTreeView.SelectedNode.Text;
                selectedMapsListBox.Items.Add(mapName);

                if (selectedMapsRemoveAllButton.Enabled == false)
                    selectedMapsRemoveAllButton.Enabled = true;
            }
            
        }

        private void availableMapsAddAllButton_Click(object sender, EventArgs e)
        {
            foreach (TreeNode officialMap in availableMapsTreeView.SelectedNode.Nodes)
            {
                selectedMapsListBox.Items.Add(officialMap.Text);
            }

            if (selectedMapsListBox.Items.Count > 0)
            {
                selectedMapsRemoveAllButton.Enabled = true;
            }
        }

        private void selectedMapsRemoveButton_Click(object sender, EventArgs e)
        {
            for (int i = selectedMapsListBox.SelectedIndices.Count - 1; i >= 0; i--)
            {
                selectedMapsListBox.Items.RemoveAt(selectedMapsListBox.SelectedIndices[i]);
            }

            if (selectedMapsListBox.Items.Count == 0)
            {

                selectedMapsRemoveAllButton.Enabled = false;
            }

            selectedMapsRemoveButton.Enabled = false;
        }

        private void selectedMapsRemoveAllButton_Click(object sender, EventArgs e)
        {
            selectedMapsListBox.Items.Clear();
            

            selectedMapsRemoveAllButton.Enabled = false;
            selectedMapsRemoveButton.Enabled = false;
        }

        private void selectedMapsListBox_Click(object sender, EventArgs e)
        {
            if (selectedMapsListBox.SelectedItem != null)
            {
                //selectedMapsRemoveAllButton.Enabled = true;
                selectedMapsRemoveButton.Enabled = true;
            }
               
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}
